package futbol.tacticas.concursantes.TacticaEvasion;

import futbol.tacticas.*;
import futbol.tacticas.concursantes.Tactica;

import java.awt.Color;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.*;

/**
 *
 * @author jpt
 */
public class TacticaEvasion implements Tactica {
	
	private int nValores[][] = {
		{-140,   0, 7},
		{-110,  0, 7},
		{-60,  60, 7},
		{-60, -60, 7},
		{0, 20, 7},
		{ 0,  -20, 7},
		{ 50,  50, 7},
		{ 50, -50, 7},
		{  100, 80, 7},
		{  120,  0, 7},
		{  100, -80, 7}
	};

	
	private EVJugPropio[] jugPropio;
	private EVJugAjeno[] jugAjeno;
	private EVJugador[] jugs; //acceso secuencial a todos los jugadores
	private EVBola bola;
	int dbola[];
	int time=-1;
	
	public TacticaEvasion() {
		bola=new EVBola();
		jugPropio=new EVJugPropio[11];
		jugAjeno=new EVJugAjeno[11];
		jugs=new EVJugador[22];
		for (int i=0; i<11; i++) {
			jugPropio[i]=new EVJugPropio(10-nValores[i][2]);
			jugAjeno[i]=new EVJugAjeno();
			jugs[i]=jugPropio[i];
			jugs[i+11]=jugAjeno[i];
		}
		dbola=new int[22];
		for (int i=20; i>=0; i--) dbola[i]=dbola[i+1]+i+1;
	}
	
	public String getNombre() {
		return "Evasion";
	}

	public Color getColor1() {
		return Color.white;
	}

	public Color getColor2() {
		return Color.white;
	}

	public Color getColorNumero() {
		return Color.black;
	}

	public int getFuerza(int n) {
		return nValores[n][2];
	}

	public int getXInicial(int n) {
		return nValores[n][0];
	}

	public int getYInicial(int n) {
		return nValores[n][1];
	}


	public Vector<Comando> getComandos(SituacionJugadores sj) {
		time++;
		bola.update(sj.getBola().getX(), sj.getBola().getY());
		for (int i=0; i<11; i++) {
			jugPropio[i].update(sj.getMisJugadores()[i].getX(), sj.getMisJugadores()[i].getY());
			jugAjeno[i].update(sj.getContrario()[i].getX(), sj.getContrario()[i].getY());
		}
		
		Vector<Comando> comandos;
		comandos=estrategia3(sj);
		return comandos;
	}

	private Vector<Comando> estrategia3(SituacionJugadores sj) {
		Vector<Comando> comandos = new Vector();
		
		for (int n=0; n<11; n++) {
			comandos.add(new ComandoIrA(n, nValores[n][0], nValores[n][1]));
		}
		defensa(comandos);
		
		for (int i=0; i<11; i++) {
			if (jugPropio[i].tieneBola(bola)) {
				if (bola.getPos().distance(160, 0) < 140 && 
					Math.abs(Math.atan2(bola.getY(), 160-bola.getX()))<Math.PI*0.40) {
					comandos.add(tiro(i));
					continue;
				}
				
				int ff=jugPropio[i].getFuerza();
				EVPase evpase=new EVPase(i, ff);
				for (int j=ff; j>0; j--) {
					double ang=evpase.mejorAngulo(j);
					double pex=evpase.pExito(j, ang);
				}
				EVCampo evc=new EVCampo();
				double[] datos=new double[2];
				double pmejor=evpase.mejorPase(evc, datos);
				
				comandos.add(pase(i, (int)datos[0], datos[1]));
			}
		}
		
		// ir por bola
		Point2D.Double pEncuentro=new Point2D.Double();
		int tmin=Integer.MAX_VALUE;
		int jmin=0;
		Point2D.Double pmin=new Point2D.Double();
		
		
		int[] t=new int[11];
		Point2D.Double[] p=new Point2D.Double[11];
		for (int i=0; i<11; i++) {
			p[i]=new Point2D.Double();
			t[i]=jugPropio[i].intercepcion(bola, p[i]);
			if (t[i]<tmin) {
				tmin=t[i];
				jmin=i;
			}
		}
		for (int i=0; i<11; i++) {
			if (t[i]-t[jmin]<=1) {
				comandos.add(new ComandoIrA(i, (int)p[i].getX(), (int)p[i].getY()));
			}
		}
		
		return comandos;
	}
	
	
	private void defensa(Vector comandos) {
		// defensa
		boolean usado[][]=new boolean[11][2];
		usado[0][0]=true; usado[1][0]=false; // porteros
		while (true) {
			double dmin=Double.MAX_VALUE;
			int j2min=0;
			for (int i=0; i<11; i++) {
				double d=jugAjeno[i].getPos().distance(-160, 0);
				if (!usado[i][1] && d<120 && d<dmin) {
					dmin=d;
					j2min=i;
				}
			}
			if (dmin==Double.MAX_VALUE) break;
			
			dmin=Double.MAX_VALUE;
			int j1min=0;
			for (int i=0; i<11; i++) {
				double d=jugAjeno[i].getPos().distance(jugAjeno[i].getX()+5, jugAjeno[i].getY());
				if (!usado[i][0] && d<dmin) {
					dmin=d;
					j1min=i;
				}
			}
			if (dmin<Double.MAX_VALUE) {
				usado[j1min][0]=true;
				usado[j2min][1]=true;
				comandos.add(new ComandoIrA(j1min, jugAjeno[j2min].getX()+5, jugAjeno[j2min].getY()));
			}
			else {
				break;
			}
		}
	}

	
	private ComandoGolpearBola pase(int j1, int f, double ang) {
		double grado=(double)f/jugPropio[j1].getFuerza()+0.005;
		double vx=Math.cos(ang);
		double vy=Math.sin(ang);

		// calculo en relacion al jugador
		double _Ax=jugPropio[j1].getX();
		double _Ay=jugPropio[j1].getY();
		double _Bx=bola.getX();
		double _By=bola.getY();
		double _b=51.0*grado+3;
		double _c=bola.getPos().distance(jugPropio[j1].getPos());
		double _a;
		if (_c<0.01) {_a=_b;}
		else {
			double _senC=((_Ax-_Bx)*vy-(_Ay-_By)*vx)/_b;
			double _cosB=((_Ax-_Bx)*vx+(_Ay-_By)*vy)/_c;
			double _senA=Math.sin(Math.PI-Math.asin(Math.abs(_senC))-Math.acos(_cosB));
			_a=Math.abs(_senA*_c/_senC);
		}
		return new ComandoGolpearBola(j1, bola.getX()+vx*_a, bola.getY()+vy*_a);
	}
	
	
	// Tiro al centro de la porteria con la maxima potencia
	private ComandoGolpearBola tiro(int j) {
		return new ComandoGolpearBola(j, 160+(160-bola.getX())*50, -bola.getY()*50);
	}

	private boolean huboGol() {
		return bola.isGol();
	}
	
	
	// devuelve bola al campo
	private int corregir(Point2D.Double p){
		int res=0;
		if (p.x> 160) {p.x= 320-p.x; res|=1;}
		if (p.x<-160) {p.x=-320-p.x; res|=2;}
		if (p.y> 120) {p.y= 240-p.y; res|=4;}
		if (p.y<-120) {p.y=-240-p.y; res|=8;}
		return res;
	}
	
	// devuelve angulo [0, 2*PI)
	private double normang(double a) {
		if (a>=0) return a%(2*Math.PI);
		else return 2*Math.PI+a%(2*Math.PI);
	}
	
	// alcance de bola en tiempo t
	private int dist_bola(int vini, int t) {
		if (vini>21  || t<0) return 0;
		if (t>vini) t=vini;
		return dbola[vini-t]-dbola[vini];
	}

	private class EVPase {
		private int NGRANO=100;
		private int[][] _sit=new int[8][]; //divide 2*PI en .length porciones
		private int[][] _tsit=new int[8][];
		private double[][] _pexitosimple=new double[8][];
		private double[][] _pexito=new double[8][];
		private int nporcion=(int)((Math.PI/5)/(2*Math.PI/NGRANO));
		private int deltaini=-(nporcion/2);
		private int deltafin=(nporcion-1)/2;
		private int jpasador;
		


		EVPase(int j, int f) {
			for (int i=0; i<8; i++) {
				_sit[i]=new int[NGRANO];
				_tsit[i]=new int[NGRANO];
				_pexitosimple[i]=new double[NGRANO];
				_pexito[i]=new double[NGRANO];
			}
			inicializar();
			jpasador=j;
			for (int i=f; i>0; i--) calcular(i);
		 }

		public void inicializar(){
			for (int i=0; i<8; i++) {
				Arrays.fill(_sit[i], 0);
				Arrays.fill(_tsit[i], -1);
				Arrays.fill(_pexitosimple[i], 0.0);
				Arrays.fill(_pexito[i], 0.0);
			}
		}
			
		
		private void calcular(int f) {
			int r1=0;
			int vini=3*f;
			int tini=3*f-14;
			if (tini<0) tini=1;
			int tfin=tini+10; // todo: mejorar
			for (int i=tini; i<=tfin; i++) {
				r1=dist_bola(vini, i);
				Circunferencia c1=new Circunferencia(bola.getPos(), r1);
				for (int j=0; j<22; j++) {
					if (j==jpasador) continue;
					double r2=jugs[j].getVelocidad()*i+10;
					double[] ang=c1.interseccion(jugs[j].getPos(), r2);
					if (ang!=null) add(ang[0], ang[1], j, _sit[f], _tsit[f]);
				}
				for (int j=0; j<NGRANO; j++)
					if (_sit[f][j]!=0 && _tsit[f][j]==-1) _tsit[f][j]=i;
			}
			calcpexitosimple(_pexitosimple[f], _sit[f]);
			calcpexito(_pexito[f], _pexitosimple[f]);
		}
			
		
		
		public double pExito(int f, double ang) {
			return _pexito[f][idx(ang)];
		}
		
		public double mejorAngulo(int f) {
			int mejor=0;
			for (int i=0; i<NGRANO; i++) {
				if (_pexito[f][i]>_pexito[f][mejor]) mejor=i;
			}
			return ang(mejor);
		}

		public double mejorPase(EVCampo evc, double datos[]) {
			double pmejor=0.0;
			int fmejor=0;
			double angmejor=0.0;
			
			for (int i=1; i<8; i++) {
				for (int j=0; j<NGRANO; j++) {
					if (_tsit[i][j]>=0) {
						double x=Math.cos(ang(j))*dist_bola(3*i, _tsit[i][j]);
						double y=Math.sin(ang(j))*dist_bola(3*i, _tsit[i][j]);
						double p=evc.pesoAtaque(x, y)*_pexito[i][j];
						if (p>pmejor) {
							pmejor=p;
							fmejor=i;
							angmejor=ang(j);
						}
					}
				}
			}
			if (datos!=null) {
				datos[0]=fmejor;
				datos[1]=angmejor;
			}
			return pmejor;
		}
					
		
		
		private void calcpexito(double[] pexito, double[] pexitosimple) {
			for (int i=deltaini; i<=deltafin; i++) {
				pexito[0]+=pexitosimple[(i+NGRANO)%NGRANO];
			}
			for (int i=1; i<NGRANO; i++) 
				pexito[i]=pexito[i-1]+(pexitosimple[((i+deltafin)+NGRANO)%NGRANO]-pexitosimple[((i+deltaini)+NGRANO)%NGRANO]);
			
			for (int i=0; i<NGRANO; i++) pexito[i]/=nporcion;
		}
			
		private void calcpexitosimple(double[] pexitosimple, int[] sit) {
			for (int i=0; i<NGRANO; i++) {
				int nprop=0;
				int najen=0;
				int situacion=sit[i];
				for (int j=0; j<11; j++) {nprop+=situacion&1; situacion>>=1;}
				for (int j=0; j<11; j++) {najen+=situacion&1; situacion>>=1;}
				if (nprop+najen==0) pexitosimple[i]=0;
				else pexitosimple[i]=(double)nprop/(nprop+najen);
			}
		}
		
		private void add(double angini, double angfin, int jug, int[] sit, int[] tsit) {
			int i;
			for (i=idx(angini); ; i=(i+1)%NGRANO) {
				if (tsit[i]==-1) sit[i]|=1<<jug;
				if (i==idx(angfin)) break;
			}
		}
		
		private int idx(double ang) {
			return (int)(ang/(2*Math.PI/NGRANO));
		}
		
		private double ang(int idx) {
			return (idx+0.5)*2*Math.PI/NGRANO;
		}
	}
	
	private class Circunferencia {
		private Point2D.Double pos;
		private double r;
		
		Circunferencia(Point2D.Double pos, double r) {
			this.pos=new Point2D.Double();
			this.pos.setLocation(pos);
			this.r=r;
		}
		
		// devuelve los 2 angulos de interseccion, [0,2*PI), 
		// del primero al segundo en sentido antihorario hay interseccion de circulos
		double[] interseccion(Point2D.Double pos2, double r2) {
			double d;
			if ((d=pos.distance(pos2))>r+r2 || r>d+r2 || r2>d+r) return null;
			double[] ang=new double[2];
			double a=Math.acos((d*d+r*r-r2*r2)/(2*r*d));
			double b=Math.atan2(pos2.getY()-pos.getY(), pos2.getX()-pos.getX());
			ang[0]=normang(b-a);
			ang[1]=normang(b+a);
			return ang;
		}
	}
	
	private class EVCampo {
		double pesoAtaque(double x, double y) {
			//if (true) return 1.0;
			if (Math.abs(x)>160 || Math.abs(y)>120) return 0.0;
			
			if (x<-80) return 0.05;
			else if (x<80) return 0.05+(0.7-0.05)*(x-(-80))/(80-(-80));
			else if (Math.hypot(160-x, y)<100 && Math.abs(Math.atan2(y, 160-x))<Math.PI/3) return 1;
			else return 0.7;
		}
	}
	
	private class EVBola {

		public EVBola() {
			hBola=new Point2D.Double[] {new Point2D.Double(), new Point2D.Double()};
			vB=new Point2D.Double();
			mB=0.0;
			nhBola=0;
		}
		
		public EVBola(Point2D.Double pos, Point2D.Double dir, int vel){
			this();
			if (pos!=null) hBola[0].setLocation(pos);
			if (dir!=null) {
				mB=vel;
				double d=Math.hypot(dir.getX(), dir.getY());
				if (d>0.0)
				vB.setLocation(dir.getX()/d, dir.getY()/d);
			}
		}
		
		public double getUx() {
			return vB.x;
		}
		
		public double getUy() {
			return vB.y;
		}
		
		public Point2D.Double getU() {
			return vB;
		}
		
		public double getM() {
			return mB;
		}
		
		public double getX() {
			return hBola[0].x;
		}
		
		public double getY() {
			return hBola[0].y;
		}
		
		public Point2D.Double getPos() {
			return hBola[0];
		}
		
		public boolean isGol() {
			return gol;
		}
	
		public void proyectar(int t, Point2D.Double dest) {
			if (dest==null) return;
			int d=0;
			for (int i=0, j=(int)Math.round(mB); i<t && j>0; i++, j--) d+=j;
			dest.setLocation(getX()+getUx()*d, getY()+getUy()*d);
			corregir(dest);
		}
		
		public void update(double x, double y) {
			hBola[1].setLocation(hBola[0]);
			hBola[0].setLocation(x, y);
			
			if (Math.abs(hBola[0].getX())>=160.0 && Math.abs(hBola[0].getY())<=30.0) gol=true;
			else if (hBola[1].distance(hBola[0])>100.0) gol=true;
			else gol=false;
			
			if (gol) {
				mB=0.0;
				return;
			}
			
			Point2D.Double bAux=new Point2D.Double(hBola[1].x+vB.x*mB, hBola[1].y+vB.y*mB);
			int res=corregir(bAux);
			if (nhBola==0 || mB<0.1 || hBola[0].distance(bAux)>2.0) {
				// bola golpeada o parada
				mB=hBola[0].distance(hBola[1]);
				if (mB>0.1) {
					vB.x=(hBola[0].x-hBola[1].x)/mB;
					vB.y=(hBola[0].y-hBola[1].y)/mB;
					mB=Math.round(mB)-1.0;
					nhBola=1;
				}
				else {
					vB.setLocation(0, 0);
					mB=0.0;
					nhBola=0;
				}
				return;
			}
			// bola continua su trayectoria
			double dd=(mB+nhBola+mB+1)*nhBola/2.0;
			bAux.setLocation(hBola[1].x-vB.x*dd, hBola[1].y-vB.y*dd);
			if ((res&1)>0) {bAux.x= 320-bAux.x;}
			if ((res&2)>0) {bAux.x=-320-bAux.x;}
			if ((res&4)>0) {bAux.y= 240-bAux.y;}
			if ((res&8)>0) {bAux.y=-240-bAux.y;}
			dd=hBola[0].distance(bAux);
			vB.x=(hBola[0].x-bAux.x)/dd;
			vB.y=(hBola[0].y-bAux.y)/dd;
			nhBola++;
			mB=dd/nhBola-(double)(nhBola+1)/2.0;
			mB=Math.round(mB);
		}
		
	
		private Point2D.Double[] hBola;
		private Point2D.Double vB;
		private double mB;
		private int nhBola;
		// huboGol==true cuando balon sobrepasa porteria y cuando vuelve posicion inicial (2 ticks)
		private boolean gol; 
	}
	
	private class EVJugPropio extends EVJugador {
		public EVJugPropio(int velocidad) {
			super();
			this.velocidad=velocidad;
		}
		
		public boolean isPropio() { return true; }
		
		public void update(double x, double y) {
			if (huboGol()) {
				pos.setLocation(x, y);
				return;
			}
			pos.setLocation(x, y);
		}
	}
			  
	private class EVJugAjeno extends EVJugador {
		public EVJugAjeno() {
			super();
			this.velocidad=3;
			hPos=new Point2D.Double[4];
			for (int i=0; i<hPos.length; i++) hPos[i]=new Point2D.Double();
		}
		
		public boolean isPropio() { return false; }
		
		public void update(double x, double y) {
			if (huboGol() || primerUpdate) {
				pos.setLocation(x, y);
				for (int i=0; i<hPos.length; i++) hPos[i].setLocation(pos);
				primerUpdate=false;
				return;
			}
			
			pos.setLocation(x, y);

			double dacum=0.0;
			for (int i=hPos.length-1; i>0; i--) {
				dacum+=hPos[i].distance(hPos[i-1]);
				hPos[i].setLocation(hPos[i-1]);
			}
			dacum+=hPos[0].distance(pos);
			hPos[0].setLocation(pos);
			int v=(int)Math.floor(dacum/hPos.length);
			if (v>velocidad && v<=6) velocidad=v;
		}
		
		private boolean primerUpdate=true;
		private Point2D.Double[] hPos;
	}
	
	private abstract class EVJugador {
		protected int velocidad;
		protected Point2D.Double pos;
		protected Point2D.Double uAct;
		protected double mAct;
		
		public EVJugador() {
			velocidad=3;
			pos=new Point2D.Double();
			uAct=new Point2D.Double();
			mAct=0.0;
		}
		
		public double getX() {
			return pos.x;
		}
				  
		public double getY() {
			return pos.y;
		}
		
		public Point2D.Double getPos() {
			return pos;
		}
		
		public int getVelocidad() {
			return velocidad;
		}
		
		public int getFuerza() {
			return 10-velocidad;
		}
		
		public Point2D.Double getU() {
			return uAct;
		}
		public double getM() {
			return mAct;
		}
		
		public int intercepcion(EVBola b, Point2D.Double lugar) {
			int t=0;
			int v=(int)Math.round(b.getM());
			Point2D.Double bProy=new Point2D.Double();
			if (v>14) t=v-14;
			for (; t<200; t++) {
				b.proyectar(t, bProy);
				if (getPos().distance(bProy)<t*getVelocidad()+10) {
					if (lugar!=null) lugar.setLocation(bProy);
					return t;
				}
			}
			return t;
		}
		
		public boolean tieneBola(EVBola b) {
			if (pos.distanceSq(b.getPos())<100) return true;
			return false;
		}
		
		abstract public boolean isPropio();
		
		abstract public void update(double x, double y);  
	}
}
